﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace FloatBall
{
    static class Water
    {
        //Public constants
        public const int NUM_X = 160;
        public const int NUM_Y = 100;
        public const float DT = 0.05f;
        public const float DAMP_FACTOR = 0.992f;
        public const float NORMAL_CONST = 30f;

        //Public variables and objects
        public static Texture2D waterTexture;
        public static VertexPositionNormalTexture[] node;
        public static short[] triangleListIndices;

        //Private constants
        private const int NUM_NODES = NUM_X * NUM_Y;

        //Private variables and objects
        private static bool[,] isNotRigid;
        private static float[,] dampFactor;
        private static float[,] vel;
        private static float[,] alt;
        
        private static long t = 0;
        private static bool even = false;

        private static VertexDeclaration vertexDeclaration;
        private static VertexBuffer vertexBuffer;
        private static Random rand = new Random();





        public static void ResetWater()
        {
            for (int x = 0; x < NUM_X; x++)
                for (int y = 0; y < NUM_Y; y++)
                {
                    isNotRigid[x, y] = true;
                    dampFactor[x, y] = DAMP_FACTOR;
                    alt[x, y] = 0;
                    vel[x, y] = 0;
                }        
        }



        public static void Init(float sizeX, float sizeY)
        {
            //Initialice and reset water physics arrays
            isNotRigid = new bool[NUM_X, NUM_Y];
            dampFactor = new float[NUM_X, NUM_Y];
            alt = new float[NUM_X, NUM_Y];
            vel = new float[NUM_X, NUM_Y];
            ResetWater();


            //Point list
            node = new VertexPositionNormalTexture[NUM_NODES];

            for (int y = 0; y < NUM_Y; y++)
            {
                for (int x = 0; x < NUM_X; x++)
                {
                    Vector3 pos = new Vector3((float)x * sizeX / (float)(NUM_X - 1), 0f, (float)y * sizeY / (float)(NUM_Y - 1));
                    node[(y * NUM_X) + x] = new VertexPositionNormalTexture(pos, Vector3.UnitY, new Vector2(pos.X * 0.002f, pos.Z * 0.002f));
                }
            }
            
            
            //Triangle list
            triangleListIndices = new short[(NUM_X - 1) * (NUM_Y - 1) * 6];

            for (int y = 0; y < NUM_Y - 1; y++)
            {
                for (int x = 0; x < NUM_X - 1; x++)
                {
                    int i = (y * (NUM_X - 1) + x) * 6;

                    triangleListIndices[i + 0] = (short)(y * NUM_X + x);
                    triangleListIndices[i + 1] = (short)(y * NUM_X + x + 1);
                    triangleListIndices[i + 2] = (short)((y + 1) * NUM_X + x);

                    triangleListIndices[i + 3] = (short)(y * NUM_X + x + 1);
                    triangleListIndices[i + 4] = (short)((y + 1) * NUM_X + x + 1);
                    triangleListIndices[i + 5] = (short)((y + 1) * NUM_X + x);
                }
            }

            vertexDeclaration = VertexPositionNormalTexture.VertexDeclaration;
            vertexBuffer = new VertexBuffer(Game1.graphicsDevice, vertexDeclaration, NUM_NODES, BufferUsage.None);
            vertexBuffer.SetData<VertexPositionNormalTexture>(node);



            
            drop();
            drop();
            drop();
            drop();
            drop();
            
            
        }



        public static float ApplyBoyancy(ref Vector3 pos, ref Vector3 velBoat, ref float pitchSpeed, ref float pitch, ref float rollSpeed, ref float roll, ref float dir, ref BoatTypes.BoatType type, ref Vector3 dirFront)
        {
            
            
            //Räkna ut vattnets medelnivå under båten
            double rot = (double)dir;
            float l = 0.50f * type.length;
            float w = 0.40f * type.width;
            float xFactor = (float)(NUM_X - 1) / Level.LENGTH_X;
            float zFactor = (float)(NUM_Y - 1) / Level.WIDTH_Z;
            float sine = (float)Math.Sin(rot);
            float cosine = (float)Math.Cos(rot);
            

            int bowX = (int)MathHelper.Clamp(0.5f + (pos.X + l * sine) * xFactor, 1, NUM_X - 2);
            int bowZ = (int)MathHelper.Clamp(0.5f + (pos.Z + l * cosine) * zFactor, 1, NUM_Y - 2);

            int sternX = (int)MathHelper.Clamp(0.5f + (pos.X - l * sine) * xFactor, 1, NUM_X - 2);
            int sternZ = (int)MathHelper.Clamp(0.5f + (pos.Z - l * cosine) * zFactor, 1, NUM_Y - 2);

            int leftX = (int)MathHelper.Clamp(0.5f + (pos.X + w * cosine) * xFactor, 1, NUM_X - 2);
            int leftZ = (int)MathHelper.Clamp(0.5f + (pos.Z - w * sine) * zFactor, 1, NUM_Y - 2);

            int rightX = (int)MathHelper.Clamp(0.5f + (pos.X - w * cosine) * xFactor, 1, NUM_X - 2);
            int rightZ = (int)MathHelper.Clamp(0.5f + (pos.Z + w * cosine) * zFactor, 1, NUM_Y - 2);


            float bowDisp = MathHelper.Clamp(alt[bowX, bowZ] - pos.Y - l * pitch, 0, 10);
            float sternDisp = MathHelper.Clamp(alt[sternX, sternZ] - pos.Y + l * pitch, 0, 10);
            float leftDisp = MathHelper.Clamp(alt[leftX, leftZ] - pos.Y - w * roll, 0, 10);
            float rightDisp = MathHelper.Clamp(alt[rightX, rightZ] - pos.Y + w * roll, 0, 10);


            float targetPitch = (bowDisp - sternDisp) / (2 * l);
            pitchSpeed += 0.05f * (targetPitch - pitch);
            pitchSpeed *= 0.99f;

            float targetRoll = (leftDisp - rightDisp) / (2 * w);
            rollSpeed += 0.05f * (targetRoll - roll);
            rollSpeed *= 0.99f;

            pitch += 1f * pitchSpeed;
            pitch *= 0.94f;

            roll += 0.6f * rollSpeed;
            roll *= 0.94f;

            
            Vector3 velForward = Vector3.Dot(velBoat, dirFront) * dirFront;
            Vector3 velSide = new Vector3(velBoat.X - velForward.X, 0, velBoat.Z - velForward.Z);
            

            float totalDisp = bowDisp + rightDisp + leftDisp + sternDisp;
            float speed = velForward.Length();

            velBoat.Y += type.boyancy * totalDisp * (speed + type.planeAbility)/type.planeAbility;
            Vector3 velUp = new Vector3(0, velBoat.Y, 0);

            
            velForward *= type.forwardResistance / (type.forwardResistance + totalDisp);

            velSide *= type.sideStability/(totalDisp + type.sideStability);
            velUp *= 40 / (totalDisp + 40);

            velBoat = velForward + velSide + velUp;

            velBoat.X -= (sine * targetPitch + cosine * targetRoll) * totalDisp / type.mass;
            velBoat.Z -= (cosine * targetPitch - sine * targetRoll) * totalDisp / type.mass;

            float feedback = MathHelper.Min(60, totalDisp * velForward.LengthSquared());
            const float svall = 0.02f;

            alt[sternX, sternZ]     -= svall * 3f * feedback;
            alt[sternX + 1, sternZ] -= svall * 2f * feedback;
            alt[sternX, sternZ + 1] -= svall * 2f * feedback;
            alt[sternX, sternZ - 1] -= svall * 2f * feedback;
            alt[sternX - 1, sternZ] -= svall * 2f * feedback;

            alt[sternX + 1, sternZ + 1] -= svall * 1f * feedback;
            alt[sternX + 1, sternZ - 1] -= svall * 1f * feedback;
            alt[sternX - 1, sternZ + 1] -= svall * 1f * feedback;
            alt[sternX - 1, sternZ - 1] -= svall * 1f * feedback;
            

            return totalDisp;
        }



        public static void drop()
        {
            finger(rand.Next(0, NUM_X), rand.Next(0, NUM_Y));
        }

        private static void finger(int x, int y)
        {
            for (int dx = -20; dx <= 20; dx++)
                for (int dy = -20; dy <= 20; dy++)
                {
                    if (x + dx >= 0 && x + dx < NUM_X && y + dy >= 0 && y + dy < NUM_Y)
                    {
                        double d = Math.Sqrt(dx * dx + dy * dy);
                        if (d < 20)
                            alt[x + dx, y + dy] = 20f * (float)Math.Cos(d * Math.PI * 3 / 40);

                        //if (alt[x + dx, y + dy] > 7 * Math.Cos(d*3*Math.PI/8))
                        //    alt[x + dx, y + dy] = (d - 20) * 1f;
                    }

                }

        }




        public static void Update()
        {
            float w, delta;
            int x, y;

            t++;
            //drop();

            even = ((t % 2) == 0);


            //Simulera punkter som inte ligger i kanten
            for (x = 1 + (int)(t % 2); x < NUM_X - 1; x+=2)
                for (y = 1; y < NUM_Y - 1; y++)
                {
                    w = 0;
                    delta = 0;

                    if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                    if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                    if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                    if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }
                    
                    vel[x, y] += DT * delta / w;
                    vel[x, y] *= dampFactor[x, y];
                }


            if (even)
            {
                //Simulera raden y = 0
                y = 0;
                for (x = 1; x < NUM_X - 1; x++)
                {
                    w = 0; delta = 0;

                    if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                    if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                    if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }

                    vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];
                }


                //Simulera raden y = NUM_Y - 1
                y = NUM_Y - 1;
                for (x = 1; x < NUM_X - 1; x++)
                {
                    w = 0; delta = 0;
                    if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                    if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                    if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                    vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];
                }
            }
            else
            {
                //Simulera raden x = 0
                x = 0;
                for (y = 1; y < NUM_Y - 1; y++)
                {
                    w = 0; delta = 0;

                    if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                    if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                    if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }
                    vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];
                }

                //Simulera raden x = NUM_X - 1
                x = NUM_X - 1;
                for (y = 1; y < NUM_Y - 1; y++)
                {
                    w = 0; delta = 0;

                    if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                    if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                    if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }
                    vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];
                }


                //Simulera noden (x, y) = (0, 0)
                x = 0; y = 0; w = 0; delta = 0;
                if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }
                if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];

                //Simulera noden (x, y) = (0, NUM_Y-1)
                x = 0; y = NUM_Y - 1; w = 0; delta = 0;
                if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                if (isNotRigid[x + 1, y]) { w += 1f; delta += alt[x + 1, y] - alt[x, y]; }
                vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];

                //Simulera noden (x, y) = (NUM_X-1, 0)
                x = NUM_X - 1; y = 0; w = 0; delta = 0;
                if (isNotRigid[x, y + 1]) { w += 1f; delta += alt[x, y + 1] - alt[x, y]; }
                if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];

                //Simulera noden (x, y) = (NUM_X-1, NUM_Y-1)
                x = NUM_X - 1; y = NUM_Y - 1; w = 0; delta = 0;
                if (isNotRigid[x, y - 1]) { w += 1f; delta += alt[x, y - 1] - alt[x, y]; }
                if (isNotRigid[x - 1, y]) { w += 1f; delta += alt[x - 1, y] - alt[x, y]; }
                vel[x, y] += DT * delta / w; vel[x, y] *= dampFactor[x, y];
            }


            for (x = 0; x < NUM_X; x++)
                for (y = 0; y < NUM_Y; y++)
                    if (isNotRigid[x, y])
                        alt[x, y] += DT * vel[x, y] *100;
                    

        }


        private static void CalcNormals()
        {
            Vector3 newNormal;

            for (int y = 1; y < NUM_Y - 1; y++)
                for (int x = 1; x < NUM_X - 1; x++)
                    if (isNotRigid[x, y])
                    {
                        newNormal = Vector3.Cross(new Vector3(NORMAL_CONST, alt[x + 1, y] - alt[x - 1, y], 0), new Vector3(0, alt[x, y + 1] - alt[x, y - 1], NORMAL_CONST));
                        node[x + y * NUM_X].Normal = Vector3.Normalize(-newNormal);
                    }
        }



        public static void UpdateWaterVertices()
        {
            for (int x = 0; x < NUM_X; x++)
                for (int y = 0; y < NUM_Y; y++)
                {
                    node[x + y * NUM_X].Position.Y = alt[x, y];
                }

            CalcNormals();

        }



    }
}
